#!/sw/bin/lua

--[[
A Tarot scoring program.
TarotScore Copyright (C) 2009-2010  Andematunum-Software
(andematunum-software@googlegroups.com)

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
--]]

--[[
Valeur utilisée pour calculer le coefficient
multiplicatif des contrats. Cette valeur
correspond aux points d'une Petite.
]]
local contrat_base = 25

--[[
Liste des contrats.
]]
local contrats = {
  {"Petite",25},
  {"Garde",50},
  {"Garde sans le chien",100},
  {"Garde contre le chien",150},
  {"Grand chelem non demandé",300},
  {"Grand chelem demandé",600}
}

--[[
List des primes.
]]
local primes = {
  {"Petit au bout",10},
  {"Poignée",20},
  {"Double poignée",30},
  {"Triple poignée",40},
  {"Pas d'atout",10},
  {"Pas d'honneur",10}
}

--[[
Nombre d'atouts nécessaires pour les diverses
poignées, en fonction du nombre de joueurs.
]]
--[[
Ceci n'est pas nécessaire car le joueur est censé
connaître les nombres de cartes par poignées avant de jouer.
De plus la poignée est déclarée avant de débuter le contrat.
--]]
--[[
local poignees = {
  [3] = { 13, 15, 22 },
  [4] = { 10, 13, 15 },
  [5] = { 8, 10, 15 }
}
--]]

--[[
Nombre de points à réaliser selon
le nombre d'oudlers. L'indice dans le tableau
correspond au nombre d'oudlers.
]]
local encheres = {
  [0] = 56, 51, 41, 36
}

-- Nombre total de points dans le jeu de Tarots.
local total_points = 91

-- Nombre de parties effectuées
local ngames = 0

--[[
Calcule le coefficient multiplicateur en fonction
du contrat.
]]
function get_coefficient (contrat_num)
 contrat = contrats[contrat_num]
 value = contrat[2] -- Prendre le second champ pour la valeur du contrat
 return value / contrat_base
end

--[[
Crée un nouveau joueur.
]]
local players = {}
local player_id = 1
function create_player (name)
  -- Check if the player already exist
  for i=1,#players,1 do
    if players[i][1] == name then
      print("Player: "..name.." already exists")
      return nil
    end
  end

  players[player_id] = 
  {
    name,
    score   = 0,      -- Score actuel du joueur
    nprises = 0,      -- Nombre de fois où il a pris
    best    = 0,      -- Meilleur score (non multiplié)
    history = {}      -- Historique des scores
  }
  player=players[player_id]
  player_id = player_id + 1
  return player
end

--[[
Calcule le score de base sans primes.
]]
function calculate_contrat_score (points, contrat, noudlers)
  local seuil = encheres[noudlers]
  local diff  = points - seuil
  local coeff = get_coefficient(contrat)

  if diff >= 0 then
    return coeff*diff + contrats[contrat][2]
  else
    return coeff*diff - contrats[contrat][2]
  end
end

--[[
Calcule la valeur de la prime spécifiée.
]]
function calculate_prime (contrat, prime)
  local primeinfo = primes[prime]
  local coeff = get_coefficient(contrat)
  -- Traite le petit au bout comme prime multiplicative
  if prime == 1 then
    return coeff * primeinfo[2]
  else
    return primeinfo[2]
  end
end

--[[
Effectue la balance des scores.
]]
function balance_scores (preneur_id,score)
  for player=1,#players,1 do
    local playerinfo = players[player]
    local delta_score = 0
    if player == preneur_id then
      delta_score = score*(#players-1)
      playerinfo.nprises = playerinfo.nprises + 1
      -- Mise à jour du meilleur score
      if score > playerinfo.best then playerinfo.best = score end
    else
      delta_score = -score
    end
    -- Mise à jour de l'historique
    table.insert(playerinfo.history,delta_score)
    -- Mise à jour du score total
    playerinfo.score = playerinfo.score + delta_score
  end

  ngames = ngames+1
end

--[[
Effectue la balance des scores dans le cas d'un jeu
en équipe avec appel au Roi.
]]
function balance_scores_team(preneur_id1,preneur_id2,score)
  for player=1,#players,1 do
    local playerinfo = players[player]
    local delta_score = 0
    if (player == preneur_id1) or (player == preneur_id2) then
      delta_score = score
      playerinfo.nprises = playerinfo.nprises + 1
      -- Mise à jour du meilleur score
      if score > playerinfo.best then playerinfo.best = score end
    else
      delta_score = -score
    end
    -- Mise à jour de l'historique
    table.insert(playerinfo.history,delta_score)
    -- Mise à jour du score total
    playerinfo.score = playerinfo.score + delta_score
  end

  ngames = ngames+1
end


--[[
Met tous les scores des joueurs à zéro.
]]
function clear_scores ()
  for player=1,#players,1 do
    players[player].score = 0
    players[player].nprises = 0
    players[player].best = 0
    players[player].history = {}
  end
  ngames=0
end

--[[
Sauvegarde la base de scores dans un fichier.
]]
function save_file ()
  fd=io.open(os.getenv("HOME").."/TarotScore.txt","w")
  if not fd
  then
    print "Unable to save the score base."
    return
  end
  
  fd:write(ngames,"\n")

  for player=1,#players,1 do
    player=players[player]
    fd:write(player[1],"\n")
    fd:write(player.score,"\n")
    fd:write(player.nprises,"\n")
    fd:write(player.best,"\n")
    for game=1,ngames,1 do
      fd:write(player.history[game],"\n")
    end
  end
  fd:close()
end

--[[
Charge la base de scores depuis le fichier.
]]
function load_file ()
  fd=io.open(os.getenv("HOME").."/TarotScore.txt","r")
  if not fd then return end

  ngames=tonumber(fd:read("*line"))

  while true do
    name=fd:read("*line")
    if not name then break end
    score=tonumber(fd:read("*line"))
    nprises=tonumber(fd:read("*line"))
    best=tonumber(fd:read("*line"))
    player=create_player(name)
    player.score=score
    player.nprises=nprises
    player.best=best
    player.history={}
    for game=1,ngames,1 do
      player.history[game]=tonumber(fd:read("*line"))
    end
  end
  fd:close()
end

--[[
Interactive list selection.
]]
function select_choice (prompt,list)
  nitems = #list

  print(prompt)
  
  --[[
  This function accepts either an array of strings
  or an array of tables with first item beeing a string (key).
  ]]
  type_item = type(list[1])
  local getid = function (item) return item[1] end
  if type_item == "string" then
    getid = function (item) return item end
  end

  repeat
    for item=1,nitems,1 do
      print(item..": "..getid(list[item]))
    end
    print("Choix (validez avec entrée): ")
    choice = tonumber(io.read())
    print("")
  until choice and choice >= 1 and choice <= nitems

  return choice
end

--[[
Appelée en fin de partie pour mettre à jour
les scores.
]]
function update_scores ()
  if #players < 3 then
    print [[
Il n'y a pas encore assez de joueurs.
]]
    return
  end

  local preneur = select_choice("Choisissez le preneur:",players)     -- Sélectionne le preneur
--[[
A finaliser
]]
  -- Expérimental
  local equipier = select_choice("Choisissez l'équipier (peut être le même joueur):",players)
  -- Fin

  local contrat = select_choice("Choisissez le contrat:",contrats)    -- Sélectionne le contrat
  
  local noudlers = 0
  repeat
    io.write("Nombre d'oudlers ? ")
    noudlers = tonumber(io.read())
  until noudlers and noudlers >= 0 and noudlers <= 3

  local points = 0
  repeat
    io.write([[
Points réalisés par le preneur ?
(Ajouter un signe moins devant pour donner les points
 du tas de la défense):
]])
    points = tonumber(io.read())
  until points

  points = points<0 and total_points+points or points

  --[[
  Calcule le score sans primes.
  ]]
  local score = calculate_contrat_score(points,contrat,noudlers)

  --[[
  Ajoute les primes
  ]]
  local more_prime = { "Oui", "Non" }

  --[[
  Crée une copie locale de la liste des primes pour éviter
  les doublons.
  ]]
  local primes_copy = {}
  for p=1,#primes,1 do
    primes_copy[#primes_copy+1]=primes[p]
  end

  while #primes_copy>0 and select_choice("Ajouter une prime ?",more_prime) == 1 do
    local prime = select_choice("Choisissez une prime:",primes_copy) -- Sélectionne les primes
    table.remove(primes_copy,prime)
    local prime_score = calculate_prime(contrat,prime)
    if score >= 0 then
      score = score + prime_score
    else
      score = score - prime_score
    end
  end

  print ""
  print("Score totalisé par le preneur: ",score)
  print ""

  --[[
  Effectue la balance des scores.
  ]]
  --[[
  A finaliser
  ]]
  -- Pas de chance, le roi était sous le chien !
  if preneur == equipier
  then
    balance_scores(preneur,score)
  else
    balance_scores_team(preneur,equipier,score)
  end

  print_scores()

end

--[[
Display the score table.
]]
function print_scores ()
  if #players == 0 then
    print [[
Il n'y a encore aucun joueur.
]]
    return
  end

  print ""
  print "[[ TABLE DES SCORES ]]"
  print ""
  --[[
    La table des scores est classée par meilleur score.
    Effectue une copie semi-profonde d'abord.
  ]]
  local copy_players={}
  for player=1,#players,1 do
    copy_players[#copy_players+1]=players[player]
  end

  table.sort(copy_players,
      function(x,y)
        return x.score > y.score
      end
  )

  for player=1,#players,1 do
    local playerinfo = copy_players[player]
    print(playerinfo[1],": ",playerinfo.score," (a pris "..
        playerinfo.nprises.." fois, meilleur score: "..playerinfo.best..")")
  end
  print ""
  print ""
  print ""
end

--[[
Display the score history.
]]
function print_history ()
  if #players == 0 then
    print [[
Il n'y a encore aucun joueur.
]]
    return
  end

  if ngames == 0 then
    print [[
Aucune partie n'a encore été jouée.
]]
    return
  end

  print ""
  print "[[ HISTORIQUE DES SCORES ]]"
  print ""
  local names={}
  for player=1,#players,1 do
    local playerinfo = players[player]
    names[#names+1] = playerinfo[1]
  end
  print(unpack(names))

  for game=1,ngames,1 do
    local scores={}
    for player=1,#players,1 do
      local playerinfo = players[player]
      scores[#scores+1]=playerinfo.history[game]
    end
    print(unpack(scores))
  end

  print ""
end

--[[
Main
]]
print ""
print "TarotScore, v1.0"
print "(c) 2010 Andematunum Software"
print "andematunum@gmail.com"
print ""

--[[
Attempt to load an existing score file and load it in the base.
]]
load_file()

local menu = {
  "Créer un joueur",
  "Afficher les scores",
  "Mettre à jour les scores",
  "Mettre les scores à zéro",
  "Effacer tous les joueurs",
  "Afficher l'historique des scores",
  "Quitter"
}

while true do
  local choice = select_choice ("Que voulez-vous faire ?", menu)
  if choice == 1 then
    print("Nom du joueur: ")
    name = io.read()
    if create_player(name) then print("Joueur: "..name.." créé.") end
  elseif choice == 2 then
    print_scores()
  elseif choice == 3 then
    update_scores()
  elseif choice == 4 then
    choices={"Oui","Non"}
    choice = select_choice("Êtes-vous sûrs de vouloir effacer les scores ?",choices)
    if choice == 1 then clear_scores()
    else print "Les scores n'ont pas été effacés." end
  elseif choice == 5 then
    choices={"Oui","Non"}
    choice = select_choice("Êtes-vous sûrs de vouloir effacer les joueurs ?",choices)
    if choice == 1 then players={} player_id=1 ngames=0
    else print "Les joueurs n'ont pas été effacés." end
  elseif choice == 6 then
    print_history()
  elseif choice == 7 then
    break;
  else
    print("Désolé, je n'ai pas compris votre demande.")
  end
end

--[[
Save the current base in a file.
]]
save_file()

print [[

Merci d'avoir utilisé TarotScore !
Pour tout problème ou suggestion, me contacter à
l'adresse suivante: andematunum@gmail.com

]]

-- END OF FILE

